from erldevice import *
import random
import time
from erlitem import *
import math
from erlrays import *

from erlglobals import supportsSerialization, isAllowedPointer

# more features:
#	attractor, repeller beams
#	radio in, radio out
#	carts.. ha ha

# oh, I need to add the following components:
#	optical sensor (triggers on broken line)
#	proximity sensor (triggers on radius entered)
#	both of the above, tuned to trigger only for
#	 robots (a bit higher level)


# each component should have:
#	- bulk (sum of which is less than its parts, mostly influenced by largest components)
#	- approximate amount of material needed
#	- approximate amount of resources needed
# weight is calculated from the total resources used;
# resource need also increases (a linear factor is added) with # of components in device
# resource need can be called "complexity", and bulk -> materials needed can be a 

# formulae:
#	totalBulk = sqrt( sum( [ bulk*bulk for bulk in bulks ] ) )
#	totalNeedsMaterial = sum( [ needsMaterial[bulk] for bulk in bulks ] ) * (1 + 0.1* (2*random.random()-1) )

# all containers can contain only one item type and should use the following magic
# attributes:
#	containerCapacity - no. of items
#	containerItemType - ItemType of item.

bulkTable = {
	"very small": 1,
	"small": 5,
	"medium": 20,
	"large": 60,
	"very large": 120,
	"extremely large": 400 # this if for stuff that can really only be done with carts
}
bulkToMaterialNeedTable = {
	"very small": 1,
	"small": 7,
	"medium": 50,
	"large": 350,
	"very large": 1000,
	"extremely large": 2000,
}
complexityTable = {
	"very simple": 2,
	"simple": 4,
	"medium": 8,
	"complex": 16,
	"very complex": 32
}

class cSignalDelay (Component):
	import heapq
	componentName = "delayed signal relay"
	componentDescription = "Relays a signal after a specified number of turns.<N>On signal: stores signal, to be relayed after the specified number of turns."
	complexity, bulk = "very simple", "very small"
	specialIngredients = []
	serializedAttributes = Component.serializedAttributes + [
		"waiting"
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "delay", "integer", "the length of the delay" )
		self.initialize( "destination", "component", "destination of the signal" )
		self.waiting = []
	def tick(self):
		newWaiting = []
		for t, signal in self.waiting:
			if t <= 0:
				self["destination"].signal( signal )
			else:
				newWaiting.append( (t-1,signal) )
		self.waiting = newWaiting
	def signal(self, id):
		self.waiting.append( ( self["delay"], id ) )
supportsSerialization( cSignalDelay )
isAllowedPointer( cSignalDelay )

class cSignalGenerator (Component):
	componentName = "signal generator"
	componentDescription = "Generates a signal every turn if turned on."
	complexity, bulk = "very simple", "very small"
	specialIngredients = []
	serializedAttributes = Component.serializedAttributes + [
		"word"
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "on", "boolean", "component activation switch", HardcodedValue(True) )
		self.initialize( "destination", "component", "destination of the signal" )
		self.word = "generated-signal"
	def tick(self):
		if self["on"]:
			self["destination"].signal( self.word )
supportsSerialization( cSignalGenerator )
isAllowedPointer( cSignalGenerator )

class cSignalSplitter (Component):
	componentName = "signal splitter"
	componentDescription = "Splits one signal into two.<N>On signal: relay signal to primary and secondary destination."
	complexity, bulk = "very simple", "very small"
	specialIngredients = []
	serializedAttributes = Component.serializedAttributes + [
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "alpha", "component", "destination of primary signal" )
		self.initialize( "beta", "component", "destination of secondary signal" )
	def signal(self, id):
		self["alpha"].signal( id )
		self["beta"].signal( id )
supportsSerialization( cSignalSplitter )
isAllowedPointer( cSignalSplitter )

class cSignalRepeater (Component):
	componentName = "signal repeater"
	componentDescription = "Generates a signal every turn after receiving one input signal.<N>On signal: begin sending signals to destination."
	complexity, bulk = "very simple", "very small"
	specialIngredients = []
	serializedAttributes = Component.serializedAttributes + [
		"started"
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "destination", "component", "destination of the signal" )
		self.started = False
		self.word = "repeated-signal"
	def tick(self):
		if self.started:
			self["destination"].signal( self.word )
	def signal(self, word ):
		self.started = True
supportsSerialization( cSignalRepeater )
isAllowedPointer( cSignalRepeater )

class cAmplifierPrism (Component):
	componentName = "amplification prism"
	componentDescription = "Amplifies a ray, using additional power."
	complexity, bulk = "very complex", "medium"
	specialIngredients = [ ItemTable.BrilliantGem ]
	serializedAttributes = Component.serializedAttributes + [
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "prism", "component", "source of effect" )
		self.initialize( "power", "component", "source of energy" )
	def requestEffect(self):
		effect = self["prism"].requestEffect()
		if not effect: return None
		ampCost = effect.power * 100
		if self["power"].requestEnergy( ampCost ) < ampCost:
			return effect
		effect.power *= 2.0
		return effect
supportsSerialization( cAmplifierPrism )
isAllowedPointer( cAmplifierPrism )

class GenericItemChamber (Component):
	serializedAttributes = Component.serializedAttributes + [
		"storage",
		"containerItemType",
		"containerCapacity",
		"transferLimit",
		"transfer"
	]
	def __init__(self, itemType, capacity, transferLimit):
		Component.__init__(self)
		self.containerItemType = itemType
		self.storage = Inventory()
		self.containerCapacity = capacity
		self.transferLimit = transferLimit
	def loadedAmount(self):
		stored = self.storage.findType( self.containerItemType )
		if not stored: return 0
		return stored.amount
	def loadItems(self, item):
		if item.itemType != self.containerItemType:
			raise CannotLoadItem()
		takenAmount = min( item.amount, self.containerCapacity - self.loadedAmount() )
		taken = item.take( takenAmount )
		self.storage.add( taken )
		return item
	def reset(self):
		self.transfer = 0
	def requestObjects(self, amount):
		stored = self.storage.findType( self.containerItemType )
		if not stored: return None 
		canServe = min( amount, stored.amount, self.transferLimit - self.transfer )
		self.transfer += canServe
		return stored.take( canServe )
	def detailDescription(self):
		stored = self.storage.findType( self.containerItemType )
		if not stored:
			return "empty"
		pct = 100.0 * stored.amount / float( self.containerCapacity )
		return "%0.2lf%% full with %s" % (pct, stored.description())

class GenericGenerator (Component):
	serializedAttributes = Component.serializedAttributes + [
		"fuelType",
		"energyLimit",
		"energyPerUnit",
		"energy"
	]
	def __init__(self, itemType, energyLimit, energyPerUnit):
		Component.__init__(self)
		self.fuelType = itemType
		self.energyLimit = energyLimit
		self.energyPerUnit = float(energyPerUnit)
		self.initialize( "fuel", "component", "fuel source" )
	def reset(self):
		self.production = 0
	def requestEnergy(self, amount):
		provideLimit = min( amount, self.energyLimit - self.production )
		if provideLimit <= 0: return 0
		neededFuel = int(math.ceil( provideLimit / self.energyPerUnit ))
		fuel = self["fuel"].requestObjects( neededFuel )
		if not fuel:
			return 0
		if fuel.itemType != self.fuelType:
			raise SpectacularFailure( "generator received wrong type of fuel" )
		producedEnergy = fuel.amount * self.energyPerUnit
		self.production += producedEnergy
		return producedEnergy
		
class cLargeCoalChamber (GenericItemChamber):
	componentName = "large coal chamber"
	componentDescription = "Stores large amounts of coal."
	complexity, bulk = "simple", "large"
	specialIngredients = []
	def __init__(self):
		GenericItemChamber.__init__(self, ItemTable.Coal, 8000, 500 )
supportsSerialization( cLargeCoalChamber )
isAllowedPointer( cLargeCoalChamber )

class cCoalChamber (GenericItemChamber):
	componentName = "small coal chamber"
	componentDescription = "Stores coal."
	complexity, bulk = "simple", "medium"
	specialIngredients = []
	def __init__(self):
		GenericItemChamber.__init__(self, ItemTable.Coal, 1000, 100 )
supportsSerialization( cCoalChamber )
isAllowedPointer( cCoalChamber )

class cCoalEngine (GenericGenerator):
	componentName = "steam engine"
	componentDescription = "Generates energy, using coal as fuel."
	complexity, bulk = "medium", "large"
	specialIngredients = []
	def __init__(self):
		GenericGenerator.__init__(self, ItemTable.Coal, 200.0, 5.0 )
supportsSerialization( cCoalEngine )
isAllowedPointer( cCoalEngine )

class cBattery (Component):
	componentName = "battery"
	componentDescription = "Stores energy for later use.<N>On signal: charge battery from energy source."
	complexity, bulk = "complex", "medium"
	specialIngredients = []
	serializedAttributes = Component.serializedAttributes + [
		"transferCapacityIn",
		"transferCapacityOut",
		"storageCapacity",
		"storage",
		"transferIn",
		"transferOut",
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "power", "component", "energy source" )
		self.transferCapacityIn, self.transferCapacityOut = 250, 1000
		self.storageCapacity = 5000
		self.storage = 0
	def reset(self):
		self.transferIn, self.transferOut = 0, 0
	def requestEnergy(self, amount):
		rv = min( amount, self.storage, self.transferCapacityOut - self.transferOut )
		self.transferOut += rv
		self.storage -= rv
		return rv
	def signal(self, id):
		# On any signal... this should be a common idiom for
		# components that require only one input signal(and
		# honestly, maybe that should be all components..)
		want = self.storageCapacity - self.storage
		want = min( want, self.transferCapacityIn - self.transferIn )
		get = self["power"].requestEnergy( want )
		self.transferIn += get
		self.storage += get
supportsSerialization( cBattery )
isAllowedPointer( cBattery )


class GenericPrism (Component):
	serializedAttributes = Component.serializedAttributes + [
		"effectClass",
		"energyCost",
		"usesPerTurn",
		"uses",
	]
	def __init__(self, effectClass, usesPerTurn, energyCost):
		Component.__init__(self)
		self.effectClass = effectClass
		self.energyCost = energyCost
		self.usesPerTurn = usesPerTurn
		self.initialize( "power", "component", "energy source" )
	def reset(self):
		self.uses = 0
	def requestEffect(self):
		if self.uses >= self.usesPerTurn:
			return None
		if self["power"].requestEnergy( self.energyCost ) < self.energyCost:
			return None
		self.uses += 1 
		return self.effectClass()

class cHeatPrism (GenericPrism):
	componentName = "heat prism"
	complexity, bulk = "very complex", "medium"
	specialIngredients = [ ItemTable.FireGem ]
	componentDescription = "Focuses rays that can do damage to targets by scorching them."
	def __init__(self):
		GenericPrism.__init__(self, HeatEffect, 3, 100.0 )
supportsSerialization( cHeatPrism )
isAllowedPointer( cHeatPrism )

class cFreezePrism (GenericPrism):
	componentName = "freeze prism"
	complexity, bulk = "very complex", "medium"
	specialIngredients = [ ItemTable.FreezeGem ]
	componentDescription = "Focuses rays that can immobilize targets by freezing them."
	def __init__(self):
		GenericPrism.__init__(self, FreezeEffect, 1, 200.0 )
supportsSerialization( cFreezePrism )
isAllowedPointer( cFreezePrism )

class cRepellerPrism (GenericPrism):
	componentName = "repeller prism"
	complexity, bulk = "very complex", "medium"
	specialIngredients = [ ]
	componentDescription = "Focuses rays that push targets away from the firing point."
	def __init__(self):
		GenericPrism.__init__(self, RepelEffect, 3, 100.0 )
supportsSerialization( cRepellerPrism )
isAllowedPointer( cRepellerPrism )

class cAttractorPrism (GenericPrism):
	componentName = "attractor prism"
	complexity, bulk = "very complex", "medium"
	specialIngredients = [ ]
	componentDescription = "Focuses rays that pull targets towards the firing point."
	def __init__(self):
		GenericPrism.__init__(self, AttractEffect, 3, 100.0 )
supportsSerialization( cRepellerPrism )
isAllowedPointer( cRepellerPrism )

class cBomb (Component):
	componentName = "bomb"
	complexity, bulk = "complex", "medium"
	specialIngredients = [ ItemTable.Explosive ]
	componentDescription = "Can be detonated to destroy the device and do damage in an area around it.<N>On signal: detonate and self-destruct."
	def __init__(self):
		Component.__init__(self)
	def signal(self, name):
		host = self.device.host() # the host is a "duck" with facing, level, and position (.x, .y)
		effect = HeatEffect()
		effect.power = 10.0
		projector = EffectProjectorRadialMedium( self.device.game.interface, host.level, effect, (host.x, host.y) )
		projector.area.append( (host.x, host.y) ) #hack!
		projector.display()
		projector.display()
		projector.project()
		self.device.hostProvider.selfdestruct( violently = True ) #hack!
supportsSerialization( cBomb )
isAllowedPointer( cBomb )


class cLineProjector (Component):
	componentName = "beam projector"
	complexity, bulk = "complex", "medium"
	componentDescription = "Projects a ray in a straight line.<N>On signal: projects ray from attached prism."
	specialIngredients = [ ]
	serializedAttributes = Component.serializedAttributes + [
		"useLimit",
		"uses",
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "prism", "component", "source of ray essence" )
		self.useLimit = 3
	def reset(self):
		self.uses = 0
	def signal(self, id):
		if self.uses >= self.useLimit: return
		effect = self[ "prism" ].requestEffect()
		if not effect: return
		effect.power *= 1.0 # a.k.a.; remember to modify this for other projectors!
		self.uses += 1
		host = self.device.host() # the host is a "duck" with facing, level, and position (.x, .y)
		projector = EffectProjectorLine( self.device.game.interface, host.level, effect, (host.x, host.y), host.facing )
		projector.project()
supportsSerialization( cLineProjector )
isAllowedPointer( cLineProjector )

class cBurstProjector (Component):
	componentName = "burst projector"
	complexity, bulk = "complex", "medium"
	componentDescription = "Projects a ray in a 360-degree burst.<N>On signal: projects ray from attached prism."
	specialIngredients = [ ]
	serializedAttributes = Component.serializedAttributes + [
		"useLimit",
		"uses",
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "prism", "component", "source of ray essence" )
		self.useLimit = 3
	def reset(self):
		self.uses = 0
	def signal(self, id):
		if self.uses >= self.useLimit: return
		effect = self[ "prism" ].requestEffect()
		if not effect: return
		effect.power *= 0.5 # a.k.a.; remember to modify this for other projectors!
		self.uses += 1
		host = self.device.host() # the host is a "duck" with facing, level, and position (.x, .y)
		projector = EffectProjectorRadialMedium( self.device.game.interface, host.level, effect, (host.x, host.y) )
		projector.project()
supportsSerialization( cBurstProjector )
isAllowedPointer( cBurstProjector )

class cBallProjector (Component):
	componentName = "ball projector"
	complexity, bulk = "complex", "medium"
	componentDescription = "Projects a ray in an energy projectile that can bounce off walls.<N>On signal: projects ray from attached prism."
	specialIngredients = [ ]
	serializedAttributes = Component.serializedAttributes + [
		"useLimit",
		"uses",
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "prism", "component", "source of ray essence" )
		self.useLimit = 3
	def reset(self):
		self.uses = 0
	def signal(self, id):
		if self.uses >= self.useLimit: return
		effect = self[ "prism" ].requestEffect()
		if not effect: return
		effect.power *= 1.0 # a.k.a.; remember to modify this for other projectors!
		self.uses += 1
		host = self.device.host() # the host is a "duck" with facing, level, and position (.x, .y)
		projector = EffectProjectorBouncerBallLong( self.device.game.interface, host.level, effect, (host.x, host.y), host.facing)
		projector.project()
supportsSerialization( cBallProjector )
isAllowedPointer( cBallProjector )

class cWideProjector (Component):
	componentName = "wide-angle projector"
	complexity, bulk = "complex", "medium"
	componentDescription = "Projects a ray in a wide-angle beam.<N>On signal: projects ray from attached prism."
	specialIngredients = [ ]
	serializedAttributes = Component.serializedAttributes + [
		"useLimit",
		"uses",
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "prism", "component", "source of ray essence" )
		self.useLimit = 3
	def reset(self):
		self.uses = 0
	def signal(self, id):
		if self.uses >= self.useLimit: return
		effect = self[ "prism" ].requestEffect()
		if not effect: return
		effect.power *= 0.5 # a.k.a.; remember to modify this for other projectors!
		self.uses += 1
		host = self.device.host() # the host is a "duck" with facing, level, and position (.x, .y)
		projector = EffectProjectorWideAngleLarge( self.device.game.interface, host.level, effect, (host.x, host.y), host.facing)
		projector.project()
supportsSerialization( cWideProjector )
isAllowedPointer( cWideProjector )

class cTrigger (Component):
	componentName = "trigger"
	complexity, bulk = "very simple", "small"
	specialIngredients = [ ]
	componentDescription = "A trigger mechanism."
	serializedAttributes = Component.serializedAttributes + [
	]
	def __init__(self):
		Component.__init__(self)
		self.initialize( "destination", "component", "destination of the signal" )
	def pullTrigger(self):
		self["destination"].signal( "trigger-signal" )
supportsSerialization( cTrigger )
isAllowedPointer( cTrigger )

#not sure I'll be using the hyperprism concept
class GenericHyperprism (Component): # the name is pure technobabble; this sort of prism requires discrete "fuel" for each beam
	serializedAttributes = Component.serializedAttributes + [
		"usesPerTurn",
		"effectClass",
		"energyCost",
		"fuelType",
		"fuelCost",
		"uses",
	]
	def __init__(self, effectClass, usesPerTurn, energyCost, fuelType, fuelCost):
		Component.__init__(self)
		self.usesPerTurn = usesPerTurn
		self.effectClass = effectClass
		self.energyCost = energyCost
		self.fuelType = fuelType
		self.fuelCost = fuelCost
		self.initialize( "power", "component", "energy source" )
		self.initialize( "fuel", "component", "gem feeder" )
	def reset(self):
		self.uses = 0
	def requestEffect(self):
		if self.uses >= self.usesPerTurn:
			return None
		if self["power"].requestEnergy( self.energyCost ) < self.energyCost:
			return None
		obj = self["fuel"].requestObjects( self.fuelCost )
		if obj.itemType != self.fuelType:
			raise SpectacularFailure( "hyperprism got unexpected fuel type" )
		if obj.amount < self.fuelCost:
			return None
		self.uses += 1
		return self.effectClass()

